La curva di Phillips

Introduzione

Nel finire degli anni 50 l’economista della LSE A.W.Phillips osservò una relazione inversa fra disoccupazione e inflazione salariale, ovvero il tasso di crescita dei salari nominali, nell’economia britannica. Pochi anni dopo, Samuelson e Solow mostrarono che una simile relazione esisteva anche nell’economia americana, legando non più la disoccupazione solamente all’inflazione salariale, ma al tasso di crescita generale dei prezzi. La curva divenne così uno strumento utilizzato dai policy makers che, dopo aver valutato il trade-off tra inflazione e disoccupazione, mettevano in atto strategie atte a raggiungere un punto specifico della curva.

from config import *
from config import make_df_ECB
%matplotlib inline

Paper di Phillips

In questa sezione, replicheremo l’articolo originale di Phillips, utilizzando i dati forniti dalla Bank Of England (https://www.bankofengland.co.uk/statistics/research-datasets)

# Importa i dati sulla disoccupazione dal file Excel contenente dati macroeconomici del Regno Unito
unemployment = pd.read_excel('a-millennium-of-macroeconomic-data-for-the-uk.xlsx', 
                             sheet_name='A50. Employment & unemployment', 
                             skiprows=2, usecols='A,J')

# Rimuove le prime due righe che contengono informazioni non necessarie
unemployment = unemployment[2:]

# Rinomina la colonna non etichettata in 'Year' per maggiore chiarezza
unemployment.rename(columns={'Unnamed: 0': 'Year'}, inplace=True)

# Imposta l'anno come indice del DataFrame
unemployment.set_index('Year', inplace=True)

# Converte la colonna del tasso di disoccupazione in formato numerico (float)
unemployment['Unemployment rate'] = unemployment['Unemployment rate'].astype(float)

# Mostra le prime righe del DataFrame per verificare la corretta importazione dei dati
unemployment.head()
Unemployment rate
Year
1855 3.731877
1856 3.518180
1857 3.945176
1858 5.215345
1859 3.276380
# Importa i dati sui salari e l'inflazione dal file Excel contenente dati macroeconomici del Regno Unito
wages_inflation = pd.read_excel('a-millennium-of-macroeconomic-data-for-the-uk.xlsx', 
                                sheet_name='A47. Wages and prices', 
                                usecols='A,BD')

# Rinomina le colonne per una maggiore chiarezza
wages_inflation.columns = ['Year', 'Wage index']

# Rimuove le prime sei righe non necessarie
wages_inflation = wages_inflation[6:]

# Converte la colonna 'Year' in formato intero
wages_inflation['Year'] = wages_inflation['Year'].astype(int)

# Converte la colonna 'Wage index' in formato numerico (float)
wages_inflation['Wage index'] = wages_inflation['Wage index'].astype(float)

# Filtra i dati per includere solo gli anni a partire dal 1855
wages_inflation = wages_inflation[wages_inflation['Year'] >= 1855]

# Imposta l'anno come indice del DataFrame
wages_inflation.set_index('Year', inplace=True)

# Calcola la differenza centrale prima (approssimazione numerica della derivata prima) e la esprime in percentuale.
# Questa è la misura utilizzata nell'articolo originale, qui utilizzeremo invece la variazione percentuale
wages_inflation['First central difference'] = (
    (wages_inflation['Wage index'].shift(-1) - wages_inflation['Wage index'].shift(1)) / 
    (2 * wages_inflation['Wage index'])
) * 100

# Calcola la variazione percentuale anno su anno del Wage index
wages_inflation['Percent change'] = wages_inflation['Wage index'].pct_change() * 100

# Mostra le prime righe del DataFrame per verificare la corretta elaborazione dei dati
wages_inflation.head()
Wage index First central difference Percent change
Year
1855 59.0 NaN NaN
1856 59.0 -1.694915 0.000000
1857 57.0 -2.631579 -3.389831
1858 56.0 0.000000 -1.754386
1859 57.0 1.754386 1.785714
# Unisce i DataFrame 'unemployment' e 'wages_inflation' utilizzando l'indice (Year) e rimuove eventuali valori NaN
df = pd.merge(unemployment, wages_inflation, right_index=True, left_index=True).dropna()

# Suddivide il dataset in tre periodi storici distinti per analizzare la relazione tra disoccupazione e inflazione salariale
dfA = df[df.index <= 1913].copy()       # Periodo pre-1913
dfB = df[(df.index >= 1913) & (df.index <= 1948)].copy()  # 1913-1948 (tra le due guerre)
dfC = df[(df.index >= 1948) & (df.index <= 1957)].copy()  # 1948-1957 (primi anni post-bellici)

# Definisce le etichette temporali per i grafici
dates = ['1861-1913', '1913-1948', '1948-1957']

# Crea una figura con tre subplot affiancati per rappresentare ciascun periodo
fig, ax = plt.subplots(1, 3, figsize=(10, 3.5))

# Itera sui tre sotto-dataset per stimare e tracciare la curva di Phillips per ciascun periodo
for i, _ in enumerate([dfA, dfB, dfC]):
    
    # Calcola il termine di aggiustamento per evitare problemi con il logaritmo
    a = abs(_["Percent change"].min()) + 0.9
    
    # Trasforma in logaritmo il tasso di inflazione salariale e il tasso di disoccupazione
    _["Log wage inflation rate"] = np.log(_["Percent change"] + a)
    _["Log unemployment rate"] = np.log(_["Unemployment rate"])
    
    # Definisce le variabili per la regressione lineare (modello di Phillips)
    X = _["Log unemployment rate"]
    Y = _["Log wage inflation rate"]
    X = sm.add_constant(X)  # Aggiunge l'intercetta al modello di regressione
    model = sm.OLS(Y, X).fit()  # Stima il modello di regressione
    R2 = model.rsquared  # Ottiene il coefficiente di determinazione R^2
    
    # Calcola i valori predetti dalla regressione e li riconverte dalla scala logaritmica
    _["Predicted log wage inflation rate"] = model.predict(X)
    _["Predicted wage inflation rate"] = np.exp(_["Predicted log wage inflation rate"]) - a
    
    # Ordina i dati per il tasso di disoccupazione per una migliore rappresentazione grafica
    sorted_data = _.sort_values(by='Unemployment rate')
    
    # Disegna una linea orizzontale per rappresentare l'asse y=0
    ax[i].axhline(y=0, color='silver', linewidth=2)
    
    # Crea uno scatter plot con i dati osservati
    ax[i].scatter(_['Unemployment rate'], _['Percent change'], alpha=0.7, 
                  label="Osservazioni", color=bmh_colors[0])
    
    # Disegna la curva di Phillips stimata dalla regressione
    ax[i].plot(sorted_data['Unemployment rate'], sorted_data['Predicted wage inflation rate'], 
               color=bmh_colors[1], label="Curva Di Phillips")
    
    # Imposta il titolo per ciascun sottografico
    ax[i].set_title(f"UK, {dates[i]}", loc='left')
    
    # Etichette degli assi
    ax[i].set_xlabel('Tasso Di Disoccupazione')
    ax[i].set_ylabel('Tasso Di Inflazione Salariale')
    
    # Attiva la griglia
    ax[i].grid(True)
    
    # Aggiunge la legenda
    ax[i].legend()

# Migliora la disposizione dei grafici per evitare sovrapposizioni
plt.tight_layout()

Paper di Samuelson e Solow

In questa sezione, cercheremo di analizzare il fenomeno all’interno dell’economia Americana, per la quale i dati sono purtroppo frammentari (si vedano https://data.bls.gov/pdq/SurveyOutputServlet e https://fred.stlouisfed.org/series/M08142USM055NNBR)

# Carica i dati sulla disoccupazione (BLS) dal file Excel, saltando le prime 16 righe e impostando 'Year' come indice
unemp = pd.read_excel('BLS1.xlsx', skiprows=16).set_index('Year')

# Carica i dati sulle retribuzioni orarie medie da 25 industrie manifatturiere degli Stati Uniti (NBER)
earnings = pd.read_csv('Average Hourly Earnings, Twenty-Five Manufacturing Industries for United States.csv').set_index('observation_date')

# Converte l'indice di 'earnings' in formato datetime (anno-mese-giorno)
earnings.index = pd.to_datetime(earnings.index, format='%Y-%m-%d')

# Raggruppa i dati sulle retribuzioni annualmente, calcolando la media per ogni anno
earnings = earnings.groupby(pd.Grouper(freq='Y')).mean()

# Estrae solo l'anno dall'indice per facilitarne l'elaborazione
earnings.index = earnings.index.year

# Filtra i dati per gli anni dal 1929 al 1947
earnings = earnings[(earnings.index >= 1929) & (earnings.index <= 1947)]

# Rinomina la colonna per avere una denominazione chiara
earnings.columns = ['HOURLY_WAGE']

# Rinomina la colonna della disoccupazione per chiarezza
unemp.columns = ['UNEMPLOYMENT']

# Unisce i due DataFrame sui rispettivi indici (anno)
df = pd.merge(unemp, earnings, right_index=True, left_index=True)

# Calcola la variazione percentuale annuale delle retribuzioni orarie
df['PCT_CHANGE'] = df['HOURLY_WAGE'].pct_change() * 100

# Rimuove le righe con valori mancanti (NaN) creati durante il calcolo della variazione percentuale
df = df.dropna()

# Mostra le prime righe del DataFrame per verificare i dati
df.head()
UNEMPLOYMENT HOURLY_WAGE PCT_CHANGE
Year
1930 8.7 0.589000 -0.113058
1931 15.9 0.563833 -4.272779
1932 23.6 0.497583 -11.749926
1933 24.9 0.490583 -1.406800
1934 21.7 0.579750 18.175641
# Calcola il termine di aggiustamento per evitare problemi con il logaritmo
a = abs(df["PCT_CHANGE"].min()) + 0.9

# Calcola il logaritmo del tasso di variazione percentuale delle retribuzioni, aggiungendo il termine 'a'
df["LOG_VAR_PERC"] = np.log(df["PCT_CHANGE"] + a)

# Calcola il logaritmo del tasso di disoccupazione
df["LOG_UNRATE"] = np.log(df["UNEMPLOYMENT"])

# Definisce le variabili per la regressione lineare (modello di Phillips)
X = df["LOG_UNRATE"]
Y = df["LOG_VAR_PERC"]
X = sm.add_constant(X)  # Aggiunge l'intercetta al modello di regressione
model = sm.OLS(Y, X).fit()  # Stima il modello di regressione
R2 = model.rsquared  # Ottiene il coefficiente di determinazione R^2

# Calcola i valori predetti dalla regressione e li riconverte dalla scala logaritmica
df["Predicted log inflation rate"] = model.predict(X)
df["Predicted inflation rate"] = np.exp(df["Predicted log inflation rate"]) - a

# Ordina i dati per il tasso di disoccupazione per una migliore rappresentazione grafica
sorted_data = df.sort_values(by='UNEMPLOYMENT')

# Crea una figura con un solo sottografico
fig, ax = plt.subplots(1, 1)

# Disegna una linea orizzontale per rappresentare l'asse y=0
ax.axhline(y=0, color='silver', linewidth=2)

# Crea uno scatter plot con i dati osservati
ax.scatter(df['UNEMPLOYMENT'], df['PCT_CHANGE'], alpha=0.7, label="Osservazioni", color=bmh_colors[0])

# Disegna la curva di Phillips stimata dalla regressione
ax.plot(sorted_data['UNEMPLOYMENT'], sorted_data['Predicted inflation rate'], 
        color=bmh_colors[1], label="Curva Di Phillips")

# Imposta il titolo del grafico
ax.set_title(f"US, 1930-1947", loc='left')

# Etichette degli assi
ax.set_xlabel('Tasso Di Disoccupazione')
ax.set_ylabel('Tasso Di Inflazione Salariale')

# Attiva la griglia
ax.grid(True)

# Aggiunge la legenda
ax.legend()

# Migliora la disposizione dei grafici per evitare sovrapposizioni
plt.tight_layout()

E oggi?

def make_df_ECB(key, obs_name):
    """Estrae i dati dal datawarehouse della BCE"""
    url_ = 'https://sdw-wsrest.ecb.europa.eu/service/data/'  # URL di base per il servizio web della BCE
    format_ = '?format=csvdata'  # Impostazione del formato dei dati in CSV
    df = pd.read_csv(url_+key+format_)  # Legge i dati CSV dal servizio web BCE usando la chiave
    df = df[['TIME_PERIOD', 'OBS_VALUE']]  # Seleziona le colonne di interesse (periodo e valore osservato)
    df['TIME_PERIOD'] = pd.to_datetime(df['TIME_PERIOD'])  # Converte il periodo in formato datetime
    df = df.set_index('TIME_PERIOD')  # Imposta il periodo come indice del dataframe
    df.columns = [obs_name]  # Rinomina la colonna con il nome della variabile
    return df

# Chiave per il tasso di disoccupazione
unemp_rate_key = 'LFSI/Q.I9.S.UNEHRT.TOTAL0.15_74.T'  # Codice identificativo del tasso di disoccupazione
unemp_rate = make_df_ECB(unemp_rate_key, 'Unemployment Rate')  # Ottieni i dati dal database ECB

# Chiave per la wage inflation (inflazione salariale)
wage_inflation_key = 'MNA/Q.Y.I9.W2.S1.S1._Z.COM_PS._Z._T._Z.IX.V.N'  # Codice identificativo del salario per addetto (indice)
wage_inflation = make_df_ECB(wage_inflation_key, 'Compensation per employee')  # Ottieni i dati dal database ECB

wage_inflation = wage_inflation[wage_inflation.index >= '2000-01-01']  # Filtra i dati dal 2000 in poi

# Combina i dati di disoccupazione e salario in un unico dataframe
df = pd.merge(unemp_rate, wage_inflation, right_index=True, left_index=True)

# Raggruppa i dati per anno e calcola la media per il tasso di disoccupazione e l'ultimo valore per la compensazione per addetto
df = df.groupby(pd.Grouper(freq='Y'))[['Unemployment Rate', 'Compensation per employee']].agg({'Unemployment Rate':'mean',
                                                                                             'Compensation per employee':'last'})

df.index = df.index.year  # Imposta l'indice come anno
df['Wage inflation rate'] = df['Compensation per employee'].pct_change()*100  # Calcola il tasso di inflazione salariale come variazione percentuale
df = df.dropna()  # Rimuove i valori mancanti

df.head()  # Mostra le prime righe del dataframe
Unemployment Rate Compensation per employee Wage inflation rate
TIME_PERIOD
2001 8.508061 70.332653 2.705440
2002 8.753020 72.127430 2.551840
2003 9.188271 73.757671 2.260224
2004 9.399281 75.327356 2.128164
2005 9.231180 77.193789 2.477762
# Calcola il termine di aggiustamento per evitare problemi con il logaritmo
a = abs(df["Wage inflation rate"].min()) + 0.9

# Calcola il logaritmo del tasso di variazione percentuale delle retribuzioni, aggiungendo il termine 'a'
df["LOG_VAR_PERC"] = np.log(df["Wage inflation rate"] + a)

# Calcola il logaritmo del tasso di disoccupazione
df["LOG_UNRATE"] = np.log(df["Unemployment Rate"])

# Definisce le variabili per la regressione lineare (modello di Phillips)
X = df["LOG_UNRATE"]
Y = df["LOG_VAR_PERC"]
X = sm.add_constant(X)  # Aggiunge l'intercetta al modello di regressione
model = sm.OLS(Y, X).fit()  # Stima il modello di regressione
R2 = model.rsquared  # Ottiene il coefficiente di determinazione R^2

# Calcola i valori predetti dalla regressione e li riconverte dalla scala logaritmica
df["Predicted log inflation rate"] = model.predict(X)
df["Predicted inflation rate"] = np.exp(df["Predicted log inflation rate"]) - a

# Ordina i dati per il tasso di disoccupazione per una migliore rappresentazione grafica
sorted_data = df.sort_values(by='Unemployment Rate')

# Crea una figura con un solo sottografico
fig, ax = plt.subplots(1, 1)

# Disegna una linea orizzontale per rappresentare l'asse y=0
ax.axhline(y=0, color='silver', linewidth=2)

# Crea uno scatter plot con i dati osservati
ax.scatter(df['Unemployment Rate'], df['Wage inflation rate'], alpha=0.7, label="Osservazioni", color=bmh_colors[0])

# Disegna la curva di Phillips stimata dalla regressione
ax.plot(sorted_data['Unemployment Rate'], sorted_data['Predicted inflation rate'], 
        color=bmh_colors[1], label="Curva Di Phillips")

# Imposta il titolo del grafico
ax.set_title(f"EU, 2001-2024", loc='left')

# Etichette degli assi
ax.set_xlabel('Tasso Di Disoccupazione')
ax.set_ylabel('Tasso Di Inflazione Salariale')

# Attiva la griglia
ax.grid(True)

# Aggiunge la legenda
ax.legend()

# Migliora la disposizione dei grafici per evitare sovrapposizioni
plt.tight_layout()